#include <Windows.h>
#include <algorithm>
#include "PluginLoader.h"

namespace SubPlayer
{
    const string InitFunctionName = "InitSubs";
    const string GetErrorFunctionName = "GetError";
    const string GetEndTimeFunctionName = "GetEndTime";
    const string GetFirstSubFunctionName = "GetFirstSub";
    const string GetFirstSubSizeFunctionName = "GetFirstSubSize";

    PluginLoader::PluginLoader(const wstring &path):
        initFunction(&DefaultInit),
        getErrorMessage(&DefaultGetError)
    {
        LoadSupportedTypes(path);
    }

    PluginLoader::~PluginLoader()
    {
        FreeLibraries();
    }

    int PluginLoader::LoadPlugin(const wstring &type)
    {
        map<wstring, HINSTANCE>::const_iterator pluginIt = supportedTypes.find(type);

        if (pluginIt == supportedTypes.end()) {
            return 1;
        }

        FN_INIT tempInitFunc = NULL;
        if (GetFunction(pluginIt->second, tempInitFunc, InitFunctionName) != 0) {
            return 2;
        }
        FN_GET_ERROR tempGetErrorFunc = NULL;
        if (GetFunction(pluginIt->second, tempGetErrorFunc, GetErrorFunctionName) != 0) {
            return 3;
        }



        FN_GET_END_TIME tempGetEndTimeFunc = NULL;
        if (GetFunction(pluginIt->second, tempGetEndTimeFunc, GetEndTimeFunctionName) == 0) {
            getEndTimeFunc = tempGetEndTimeFunc;
        }

        FN_GET_FIRST_SUB_SIZE tempFirstSubSizeFunc = NULL;
        if (GetFunction(pluginIt->second, tempFirstSubSizeFunc, GetFirstSubSizeFunctionName) == 0) {
            FN_GET_FIRST_SUB tempFirstSubFunc = NULL;
            if (GetFunction(pluginIt->second, tempFirstSubFunc, GetFirstSubFunctionName) == 0) {
                getFirstSubSizeFunc = tempFirstSubSizeFunc;
                getFirstSubFunc = tempFirstSubFunc;
            }
            else {
                //:TODO default first size, and first sub
            }
        }
        else {
            //:TODO default first size, and first sub
        }

        initFunction = tempInitFunc;
        getErrorMessage = tempGetErrorFunc;
        return 0;
    }

    const vector<wstring> PluginLoader::GetSupportedTypesList() const
    {
        vector<wstring> typeList;
        for (map<wstring, HINSTANCE>::const_iterator it = supportedTypes.begin(); it != supportedTypes.end(); it++) {
            typeList.push_back(it->first);
        }
        return typeList;
    }

    void PluginLoader::LoadSupportedTypes(const wstring &path)
    {
        WIN32_FIND_DATAW fd;
        wstring mask = path + L"*.dll";
        HANDLE hFile = FindFirstFileExW(mask.c_str(), FindExInfoBasic, &fd, FindExSearchNameMatch, NULL, 0);
        if (hFile == INVALID_HANDLE_VALUE) {
            return;
        }
        
        HINSTANCE dllLibrary;
        wstring fullPath = path + fd.cFileName;
        wstring key = GetLibSupportedType(fullPath, dllLibrary);
        if (key.length() > 0) {
            supportedTypes.insert(std::make_pair(key, dllLibrary));
        }
        
        while (FindNextFileW(hFile, &fd) != 0) {
            fullPath = path + fd.cFileName;
            key = GetLibSupportedType(fullPath, dllLibrary);
            if (key.length() > 0) {
                supportedTypes.insert(std::make_pair(key, dllLibrary));
            }
        }
        FindClose(hFile);
    }

    wstring PluginLoader::GetLibSupportedType(const wstring &path, HINSTANCE &dllInst) const
    {
        dllInst = LoadLibraryEx(path.c_str(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
        if (dllInst != NULL) {
            FN_SUPP_TYPE lpTyp = (FN_SUPP_TYPE) GetProcAddress(dllInst, "SupportedType");
            if (lpTyp != (FN_SUPP_TYPE) NULL) {
                return lpTyp(); 
            }
        }
        return L"";
    }

    void PluginLoader::FreeLibraries()
    {
        for (map<wstring, HINSTANCE>::iterator it = supportedTypes.begin(); it != supportedTypes.end(); it++) {
            FreeLibrary(it->second);
        }
    }

    int PluginLoader::Initialize(const LPCWSTR subtitlePath, const LPCWSTR encoding) const
    {
        return initFunction(subtitlePath, encoding);
    }

    int PluginLoader::DefaultInit(const LPCWSTR, const LPCWSTR)
    {
        return -1;
    }

    long PluginLoader::GetEndTime() const
    {
        if (getEndTimeFunc != NULL) {
            return getEndTimeFunc();
        }
        else {
            return -1;
        }
    }

    LPCWSTR PluginLoader::DefaultGetError()
    {
        return L"No plugin has been loaded. Programmer must load plugin before using any function";
    }

    errno_t PluginLoader::GetFirstSub(wstring& sub, long & endTime) const 
    {
        int subSize = getFirstSubSizeFunc()+1;
        LPWSTR tempSub = new wchar_t[subSize];
        errno_t errorValue = getFirstSubFunc(&endTime, tempSub, subSize);
        if (errorValue == 0 || errorValue == _TRUNCATE) {
            sub = tempSub;
            errorValue = 0;
        }
        delete[] tempSub;
        return errorValue;
    }
}
